package org.fxkj.cloud.uaa.service.security.model.token;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.security.SignatureException;
import java.time.ZonedDateTime;
import java.util.*;

/**
 * <p>Description: </p>
 *
 * @author Wei, Wu
 * @create 2024/10/21 14:46
 */
@Component
@Slf4j
public class JwtTokenFactory {

    private final JwtSetting settings;
    public static final String USER_ID = "user_id" ;
    public static final String USERNAME = "username" ;
    public static final String AVATAR = "avatar" ;
    public static final String NICKNAME = "nickname" ;
    public static final String PHONE = "phone" ;
    public static final String ROLES = "roles" ;
    public static final String ENABLE = "enable" ;
    public static final String SCOPES = "scopes" ;
    public static final String WX_OPENID = "wxOpenid";

    public JwtTokenFactory(JwtSetting settings) {
        this.settings = settings;
    }

    public AccessJwtToken createAccessToken(SecurityUser securityUser) {
        if (CollectionUtils.isEmpty(securityUser.getAuthorities())) {
            throw new IllegalArgumentException("用户没有任何权限");
        }
        String subject = securityUser.getSubject();
        Claims claims = Jwts.claims().setSubject(subject);
        claims.put(USER_ID, securityUser.getUserId());
        claims.put(USERNAME, securityUser.getUsername());
        claims.put(AVATAR, securityUser.getAvatar());
        claims.put(NICKNAME, securityUser.getNickName());
        claims.put(PHONE, securityUser.getPhone());
        claims.put(ENABLE, securityUser.getEnable());
        claims.put(ROLES, securityUser.getAuthorities());
        claims.put(WX_OPENID,securityUser.getWxOpenid());
        ZonedDateTime currentTime = ZonedDateTime.now();

        String token = Jwts.builder().setClaims(claims).setIssuer(settings.getTokenIssuer()).setIssuedAt(Date.from(currentTime.toInstant())).setExpiration(Date.from(currentTime.plusSeconds(settings.getTokenExpirationTime()).toInstant())).signWith(SignatureAlgorithm.HS512, settings.getTokenSigningKey()).compact();
        return new AccessJwtToken(token, claims);
    }

    /**
     * 解析访问令牌
     */
    public SecurityUser parseAccessJwtToken(String token) {
        Jws<Claims> jwsClaims = parseTokenClaims(token);
        Claims claims = jwsClaims.getBody();
        String subject = claims.getSubject();
        @SuppressWarnings("unchecked") List<String> scopes = claims.get(SCOPES, List.class);
        SecurityUser securityUser = new SecurityUser(claims.get(USER_ID, Integer.class));
        securityUser.setUsername(claims.get(USERNAME, String.class));
        securityUser.setAvatar(claims.get(AVATAR, String.class));
        securityUser.setNickName(claims.get(NICKNAME, String.class));
        securityUser.setPhone(claims.get(PHONE, String.class));
        securityUser.setEnable(claims.get(ENABLE, Boolean.class));
        securityUser.setRoles(new ArrayList<>(claims.get(ROLES, List.class)));
        securityUser.setWxOpenid(claims.get(WX_OPENID,String.class));
        return securityUser;
    }


    public JwtToken createRefreshToken(SecurityUser securityUser) {
        if (StringUtils.isBlank(securityUser.getUsername())) {
            throw new IllegalArgumentException("Cannot create JWT Token without username/email");
        }


        ZonedDateTime currentTime = ZonedDateTime.now();

        Claims claims = Jwts.claims().setSubject(securityUser.getSubject());

        claims.put(SCOPES, Collections.singletonList("refreshToken"));
        claims.put(USER_ID, securityUser.getUserId().toString());
        claims.put(USERNAME, String.class);
        claims.put(AVATAR, String.class);
        claims.put(NICKNAME, String.class);
        claims.put(PHONE, String.class);
        claims.put(ENABLE, Boolean.class);
        claims.put(ROLES, Set.class);

        String token = Jwts.builder()
                .setClaims(claims)
                .setIssuer(settings.getTokenIssuer())
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(Date.from(currentTime.toInstant()))
                .setExpiration(Date.from(currentTime.plusSeconds(settings.getRefreshTokenExpTime()).toInstant()))
                .signWith(SignatureAlgorithm.HS512, settings.getTokenSigningKey())
                .compact();

        return new AccessJwtToken(token, claims);
    }

    /**
     * 解析刷新令牌
     */
    public SecurityUser parseRefreshToken(String token) {
        Jws<Claims> jwsClaims = parseTokenClaims(token);
        Claims claims = jwsClaims.getBody();
        @SuppressWarnings("unchecked")
        List<String> scopes = claims.get(SCOPES, List.class);
        SecurityUser securityUser = new SecurityUser(claims.get(USER_ID, Integer.class));
        securityUser.setUsername(claims.get(USERNAME, String.class));
        securityUser.setAvatar(claims.get(AVATAR, String.class));
        securityUser.setNickName(claims.get(NICKNAME, String.class));
        securityUser.setPhone(claims.get(PHONE, String.class));
        securityUser.setEnable(claims.get(ENABLE, Boolean.class));
        securityUser.setRoles(new ArrayList<>(claims.get(ROLES, List.class)));
        securityUser.setWxOpenid(claims.get(WX_OPENID,String.class));
        if (scopes == null || scopes.isEmpty()) {
            throw new IllegalArgumentException("Refresh Token doesn't have any scopes");
        }
        if (!scopes.get(0).equals("refreshToken")) {
            throw new IllegalArgumentException("Invalid Refresh Token scope");
        }
        return securityUser;
    }

    public Jws<Claims> parseTokenClaims(String token) {
        try {
            return Jwts.parser().setSigningKey(settings.getTokenSigningKey()).parseClaimsJws(token);
        } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) {
            log.debug("Invalid JWT Token", ex);
            throw new BadCredentialsException("Invalid JWT token: ", ex);
        } catch (ExpiredJwtException expiredEx) {
            log.debug("JWT Token is expired", expiredEx);
            throw new JwtException("JWT Token expired", expiredEx);
        }
    }
}
